/**
 * 下载全量仓库，会根据 sqlite 中保存的数据跳过已下载的文件。
 * breakpoint 表用于断点接续下载，下载任务按照深度优先策略执行，断点接续时按顺序只下载后续任务，每次到达一个新目录，更新一次数据库记录。
 * running 表用于保存正在下载的任务，开始下载时插入，下载完成后删除。断点接续时需要将表中的数据重新下载一次。
 * 由于下载进度于断点进度并不一致，所以
 * 全量下载会开启 POOL_SIZE + 2 个协程，POOL_SIZE为下载协程，另一个下载链接扫描，一个断点接续时恢复正在下载任务的协程
 */

const cheerio = require('cheerio')

const dbHelper = require('./db-helper');
const { httpGet } = require('./http-util');
const { log, wait } = require('./util')
const { TaskQueue, type } = require('./task-queue')
const { POOL_SIZE, MAVEN_REPO_URL, QUEUE_LENGTH_WAIT } = require('./config')
const { getFiles, downloadFile } = require('./download-file');

const WAIT_TIME = 2000     //队列满了之后的等待时间
const taskQueues = []       //下载文件任务队列
const scanQueues = []       //扫描任务队列
let hotGroupUrl = []       //热点仓库的groupId
let lastRunning = []    //上次退出时正在下载的任务

/**
 * 初始化下载队列
 */
function initTaskQueue() {
    for (let i = 0; i < POOL_SIZE; i++) {
        taskQueues.push(new TaskQueue(`task-${i}`, async (url) => {
            //1.下载文件
            await downloadFile(url)
            //2.删除表记录
            dbHelper.delRunning(url)
        }))

        scanQueues.push(new TaskQueue(`scan-${i}`, readFiles, type.TYPE_STACK))
    }
    taskQueues.forEach((t) => log.info(t.toString()))
    scanQueues.forEach((t) => log.info(t.toString()))
}

/**
 * 从数据库 breakpoint 表加载断点
 */
function loadBreakPoint() {
    //查询并去除空值
    let breakpoints = dbHelper.getBreakPoint().filter(url => url)
    breakpoints.forEach(pushScan)
    log.info(`加载最后一次下载的断点：${breakpoints.length}`)
    return breakpoints.length
}

/**
 * 从数据库 running 表加载上次退出时的未下载完成的文件
 */
async function loadLastRunning() {
    lastRunning = dbHelper.getRunning()
    let fail = 0
    log.info(`开始恢复异常退出时未完成的下载任务，共计 ${lastRunning.length} 条`)
    for (let i in lastRunning) {
        try {
            await downloadFile(lastRunning[i])
            dbHelper.delRunning(lastRunning[i])
            log.info(`恢复下载成功，url=${lastRunning[i]}`)
        } catch (e) {
            log.error(`恢复下载失败,不做处理, url=${lastRunning[i]}, error=${e}`)
            fail++
        }
    }
    log.info(`恢复下载结束，共计 ${lastRunning.length}，失败 ${fail}`)
}

/**
 * 从数据库加载热点仓库数据
 */
function loadHotMaven() {
    hotGroupUrl = dbHelper.getHotMaven().map(row => `${MAVEN_REPO_URL}${row.groupId.replace(/\./g, '/')}`)
    log.info(`加载热点数据 ${hotGroupUrl.length} 条`)
}

/**
 * 任务加入到任务队列，当任务数大于 QUEUE_LENGTH_WAIT 时停止加入
 * @param {*} url 
 */
async function pushTask(url) {
    let min = 9999, index
    //寻找最小值
    for (let i in taskQueues) {
        if (taskQueues[i].size() < min) {
            index = i
            min = taskQueues[i].size()
        }
    }
    if (min >= QUEUE_LENGTH_WAIT) {

        log.debug(`所有队列已满，等待 ${WAIT_TIME / 1000} 秒后重试。。`)
        await wait(WAIT_TIME)       //重试
        await pushTask(url)
    } else {
        log.debug(`加入下载队列, queue=${taskQueues[index].name}, size=${taskQueues[index].size()}, url=${url}`)
        //1.加入队列
        taskQueues[index].push(url)
        //2.加入数据库
        dbHelper.addRunning(url)
    }
}

/**
 * 向队列中加入扫描任务
 * @param {String} url 
 */
function pushScan(url) {
    let min = 9999, index
    //寻找最小值
    for (let i in scanQueues) {
        if (scanQueues[i].size() < min) {
            index = i
            min = scanQueues[i].size()
        }
    }
    log.debug(`加入扫描队列, queue=${scanQueues[index].name}, size=${scanQueues[index].size()}, url=${url}`)
    scanQueues[index].push(url)
}

/**
 * 加载
 * @param {*} url 
 */
async function readFiles(url) {
    // 开始请求
    let res = await httpGet(url)
    let $ = cheerio.load(res)

    let dirs = []
    let files = []
    $('pre a').each((i, elem) => {
        let $a = $(elem)
        if ($a.attr('href').endsWith('.jar') || $a.attr('href').endsWith('.pom')) {
            files.push(url + $a.attr('href'))
        }
        if ($a.attr('href') != '../' && $a.attr('href').endsWith('/')) {
            dirs.push(url + $a.attr('href'))
        }
    })

    // 下载文件
    for (let i in files) {
        await pushTask(files[i])
    }

    // 遍历目录
    for (let i in dirs) {
        // 保存进度
        dbHelper.addBreakPoint(dirs[i])
        pushScan(dirs[i])
    }
    // 删除已完成的url
    dbHelper.delBreakPoint(url)
}

/**
 * 判断是否为热点仓库
 * @param {String} url 
 */
function notHot(url) {
    for (let i in hotGroupUrl) {
        if (url.startsWith(hotGroupUrl[i])) {
            return false
        }
    }
    return true
}

async function main() {

    initTaskQueue()     //初始化下载队列

    //带 -da 参数时才执行
    const args = process.argv.slice(2)
    if (args && args.length && args.length > 1) {
        //包含 -r 参数，表示清除断点，重新下载
        if (args.indexOf('-r') != -1) {
            log.info('清除进度断点。')
            dbHelper.clearBreakPoint() //清除断点
            log.info('清除未完成的下载任务。')
            dbHelper.clearRunning()     //清除未完成下载
            loadHotMaven()      //加载热点库
        }

        //包含 -a 参数，表示不过滤热点仓库，开始断点下载
        if (args.indexOf('-a') != -1) {
            loadLastRunning()   //加载未完成下载
            hotGroupUrl = []        //清除热点数据
        }
    } else {
        //开始下载任务，默认过滤热点仓库，开始断点下载
        loadHotMaven()      //加载热点库
        loadLastRunning()   //加载未完成下载
    }

    log.info(`开始下载任务：下载队列长度 ${taskQueues.length}, 热点库 ${hotGroupUrl.length} 个，上次退出未完成的下载任务 ${lastRunning.length}`)
    //加载断点
    if (!loadBreakPoint() && !lastRunning.length) {
        log.info('没有检测到未完成下载，开始全量下载！')
        //如果进度为空，重新下载
        pushScan(MAVEN_REPO_URL)
        // pushScan('https://repo1.maven.org/maven2/log4j/')
    }
}

//开始运行
main()